/*
 * SharedMemory2.h
 *
 *  Created on: Nov 7, 2013
 *      Author: m
 */

#ifndef SHAREDMEMORY2_H_
#define SHAREDMEMORY2_H_

#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "../common/Exception.h"

namespace ipc
{

#define SHM_DONT_CARE 0
#define SHM_PERMS 0666

template <class T>
class SharedMemory2
{

private:
    int m_id;
    int m_size;
    T*  m_data;

    /**
     * Constructor (privado)
     * @param id   ID de la shmem
     * @param size Tamaño de la shmem
     */
    SharedMemory2( int id, int size );

public:

    /**
     * Destructor
     */
    virtual ~SharedMemory2();

    /**
     * Metodo para crear una Shared Memory
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     * @param semAmmount Cantidad de elementos en el array
     * @param flags flags del array (no tocar)
     *
     * @return La Shared Memory creada
     */
    static T* create( std::string file, int charKey, int size = 1,
            int flags = IPC_CREAT | IPC_EXCL );

    /**
     * Metodo para obtener una Shared Memory (ya creada)
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     *
     * @return La Shared Memory
     */
    static T* get( std::string file, int charKey, int size =
            SHM_DONT_CARE );

    /**
     * Para dejar de usar la shmem!!!!
     *
     * @param file
     * @param charKey
     */
    static void release( T* p );

    /**
     * Metodo para destruir una Shared Memory
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     */
    static void destroy( std::string file, int charKey );

};

template<class T>
inline SharedMemory2<T>::~SharedMemory2()
{
}

template<class T>
inline T* SharedMemory2<T>::create( std::string file, int charKey,
        int size, int flags )
{
    std::string touchCmd;
    std::string error;
    key_t key;
    int id;

    touchCmd = "touch " + file;
    system( touchCmd.c_str() );

    key = ftok( file.c_str(), charKey );

    if ( key < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error while creating the shared memory key on "
                "file:" + file + ". Error: " + error );
    }
    else
    {
        flags |= SHM_PERMS;
        id = shmget( key, sizeof(T) * size, flags );

        if ( id < 0 )
        {
            error = strerror( errno );
            throw Exception( "Error creating the shared memory: " + error );
        }
    }

    // Attach del bloque de memoria al espacio de direcciones del proceso
    T* data = (T*) shmat( id, NULL, 0 );

    if ( data < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error attaching the shared memory: " + error );
    }

    return data;
}

template<class T>
inline T* SharedMemory2<T>::get( std::string file, int charKey,
        int size )
{
    return SharedMemory2<T>::create( file, charKey, size, IPC_EXCL );
}

template<class T>
void SharedMemory2<T>::release( T* p )
{
    shmdt( p );
}

template<class T>
inline void SharedMemory2<T>::destroy( std::string file, int charKey )
{
    std::string error;
    key_t key;
    int id;

    key = ftok( file.c_str(), charKey );

    if ( key < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error while getting the shared memory key on "
                "file:" + file + ". Error: " + error );
    }
    else
    {
        id = shmget( key, 0, SHM_PERMS | IPC_EXCL );

        if ( id < 0 )
        {
            error = strerror( errno );
            throw Exception( "Error getting the shared memory: " + error );
        }
    }

    if ( shmctl( id, IPC_RMID, NULL ) < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error destroying the shared memory: " + error );
    }
}

} /* namespace ipc */
#endif /* SHAREDMEMORY2_H_ */
